/**
 * @author nttdocomo
 */
define(function(require) {
	require("./aries");
	require("./util/renderable");
	require("./util/elementContainer");
	require("./util/observable");
	require("./lang/number");
	var rspace = /\s+/;
	aries.klass('aries.AbstractComponent', Backbone.View.extend({
		renderTpl : '<%=content%>',

		/**
		 * @property {Boolean} isComponent
		 * `true` in this class to identify an object as an instantiated Component, or subclass thereof.
		 */
		isComponent : true,
		/**
		 * @cfg {String} [disabledCls='x-item-disabled']
		 * CSS class to add when the Component is disabled. Defaults to 'x-item-disabled'.
		 */
		disabledCls : aries.baseCSSPrefix + 'item-disabled',
		initialize : function(config) {
			if (config)
				$.extend(this, config);
			aries.util.ElementContainer.constructor.call(this);
			this.addEvents(config.handler)
			this.getId();
			this.setupProtoEl();
			// initComponent, beforeRender, or event handlers may have set the style or cls property since the protoEl was set up
			// so we must apply styles and classes here too.
			if (this.cls) {
				this.initialCls = this.cls;
				this.$el.addClass(this.cls);
			}
			//var $el = (new this.labelableRenderTpl(this.getLabelableRenderData())).html();
			//this.setElement($el);
			/*if(!this.initComponent){
			 debugger
			 }*/
			this.renderData = this.renderData || {};
			this.initComponent();

			if (this.renderTo) {
				this.doRender(this.renderTo);
				// EXTJSIV-1935 - should be a way to do afterShow or something, but that
				// won't work. Likewise, rendering hidden and then showing (w/autoShow) has
				// implications to afterRender so we cannot do that.
			}
			//this.constructor._initializer && this.constructor._initializer.apply(this, arguments);
		},

		/**
		 * Adds a CSS class to the top level element representing this component.
		 * @param {String/String[]} cls The CSS class name to add
		 * @return {Ext.Component} Returns the Component to allow method chaining.
		 */
		addCls : function(cls) {
			var el = this.$el;

			el.addClass(_.isString(cls) ? cls : cls.join(" "));
			return this;
		},

		/**
		 * Adds a cls to the uiCls array, which will also call {@link #addUIClsToElement} and adds to all elements of this
		 * component.
		 * @param {String/String[]} classes A string or an array of strings to add to the uiCls
		 * @param {Object} skip (Boolean) skip True to skip adding it to the class and do it later (via the return)
		 */
		addClsWithUI : function(classes, skip) {
			var me = this, clsArray = [], length, i = 0, cls;

			if ( typeof classes === "string") {
				classes = (classes.indexOf(' ') < 0) ? [classes] : Ext.String.splitWords(classes);
			}

			length = classes.length;

			me.uiCls = _.clone(_.isString(me.uiCls) ? me.uiCls.split(rspace) : me.uiCls);

			for (; i < length; i++) {
				cls = classes[i];
				if (cls && !me.hasUICls(cls)) {
					me.uiCls.push(cls);
					clsArray = clsArray.concat(me.addUIClsToElement(cls));
				}
			}

			if (skip !== true) {
				me.addCls(clsArray);
			}

			return clsArray;
		},
		//</debug>

		/**
		 * Adds the specified events to the list of events which this Observable may fire.
		 *
		 * @param {Object/String...} eventNames Either an object with event names as properties with
		 * a value of `true`. For example:
		 *
		 *     this.addEvents({
		 *         storeloaded: true,
		 *         storecleared: true
		 *     });
		 *
		 * Or any number of event names as separate parameters. For example:
		 *
		 *     this.addEvents('storeloaded', 'storecleared');
		 *
		 */
		addEvents : function(o) {
			var events = $.extend({}, this.events, o);
			this.events = events;
			/*var me = this, events = me.events || (me.events = {}), arg, args, i;

			 if ( typeof o == 'string') {
			 for ( args = arguments, i = args.length; i--; ) {
			 arg = args[i];
			 if (!events[arg]) {
			 events[arg] = true;
			 }
			 }
			 } else {
			 Ext.applyIf(me.events, o);
			 }*/
		},

		/**
		 * Disable the component.
		 * @param {Boolean} [silent=false] Passing true will supress the 'disable' event from being fired.
		 */
		disable : function(silent) {
			var me = this;
			delete me.enableOnBoxReady;
			me.addCls(me.disabledCls);
			if (me.rendered) {
				me.onDisable();
			} else {
				me.disableOnBoxReady = true;
			}

			me.disabled = true;

			if (silent !== true) {
				delete me.resetDisable;
				me.trigger('disable', me);
			}

			return me;
		},

		/**
		 * Enable the component
		 * @param {Boolean} [silent=false] Passing true will supress the 'enable' event from being fired.
		 */
		enable : function(silent) {
			var me = this;
			delete me.disableOnBoxReady;
			me.removeCls(me.disabledCls);
			if (me.rendered) {
				me.onEnable();
			} else {
				me.enableOnBoxReady = true;
			}

			me.disabled = false;
			delete me.resetDisable;

			if (silent !== true) {
				//me.fireEvent('enable', me);
			}

			return me;
		},

		getComponentLayout : function() {
			var me = this;

			if (!me.componentLayout || !me.componentLayout.isLayout) {
				me.setComponentLayout(Ext.layout.Layout.create(me.componentLayout, 'autocomponent'));
			}
			return me.componentLayout;
		},

		/**
		 * Gets the current height of the component's underlying element.
		 * @return {Number}
		 */
		getHeight : function() {
			return this.$el.height();
		},

		/**
		 * This is used to determine where to insert the 'html', 'contentEl' and 'items' in this component.
		 * @private
		 */
		getTargetEl : function() {
			return this.frameBody || this.$el;
		},

		/**
		 * @private
		 */
		getTpl : function(name) {
			return _.isFunction(this[name]) ? this[name] : _.template(this[name]);
		},

		/**
		 * @private
		 * Returns the CSS style object which will set the Component's scroll styles. This must be applied
		 * to the {@link #getTargetEl target element}.
		 */
		getOverflowStyle : function() {
			var me = this, result = null;

			if ( typeof me.autoScroll == 'boolean') {
				result = {
					overflow : me.autoScroll ? 'auto' : ''
				};
			} else if (me.overflowX !== undefined || me.overflowY !== undefined) {
				result = {
					'overflow-x' : (me.overflowX || ''),
					'overflow-y' : (me.overflowY || '')
				};
			}

			// The scrollable container element must be non-statically positioned or IE6/7 will make
			// positioned children stay in place rather than scrolling with the rest of the content
			if (result && (Ext.isIE6 || Ext.isIE7)) {
				result.position = 'relative';
			}
			return result;
		},

		/**
		 * Method to determine whether this Component is floating.
		 * @return {Boolean} the floating state of this component.
		 */
		isFloating : function() {
			return this.floating;
		},

		/**
		 * Method to manage awareness of when components are added to their
		 * respective Container, firing an #added event. References are
		 * established at add time rather than at render time.
		 *
		 * Allows addition of behavior when a Component is added to a
		 * Container. At this stage, the Component is in the parent
		 * Container's collection of child items. After calling the
		 * superclass's onAdded, the ownerCt reference will be present,
		 * and if configured with a ref, the refOwner will be set.
		 *
		 * @param {Ext.container.Container} container Container which holds the component
		 * @param {Number} pos Position at which the component was added
		 *
		 * @template
		 * @protected
		 */
		onAdded : function(container, pos) {
			var me = this;
			me.ownerCt = container;
			/*if (me.hasListeners.added) {
			 me.fireEvent('added', me, container, pos);
			 }*/
		},

		onBoxReady : function() {
			var me = this;

			if (me.disableOnBoxReady) {
				me.onDisable();
			} else if (me.enableOnBoxReady) {
				me.onEnable();
			}
			if (me.resizable) {
				me.initResizable(me.resizable);
			}

			// Draggability must be initialized after resizability
			// Because if we have to be wrapped, the resizer wrapper must be dragged as a pseudo-Component
			if (me.draggable) {
				me.initDraggable();
			}
		},

		// private
		onBlur : function(e) {
			var me = this, focusCls = me.focusCls, focusEl = me.getFocusEl();

			if (me.destroying) {
				return;
			}

			me.beforeBlur(e);
			if (focusCls && focusEl) {
				focusEl.removeClass(me.removeClsWithUI(focusCls, true));
			}
			if (me.validateOnBlur) {
				me.validate();
			}
			me.hasFocus = false;
			me.trigger('blur', me, e);
			me.postBlur(e);
		},

		/**
		 * Allows addition of behavior to the disable operation.
		 * After calling the superclass’s onDisable, the Component will be disabled.
		 *
		 * @template
		 * @protected
		 */
		onDisable : function() {
			if (this.maskOnDisable) {
				this.el.dom.disabled = true;
				//this.mask();
			}
		},

		/**
		 * Allows addition of behavior to the enable operation.
		 * After calling the superclass’s onEnable, the Component will be enabled.
		 *
		 * @template
		 * @protected
		 */
		onEnable : function() {
			if (this.maskOnDisable) {
				this.el.dom.disabled = false;
				//this.unmask();
			}
		},

		// private
		onFocus : function(e) {
			var me = this, focusCls = me.focusCls, focusEl = me.getFocusEl();

			if (!me.disabled) {
				me.preFocus(e);
				if (focusCls && focusEl) {
					focusEl.addClass(me.addClsWithUI(focusCls, true));
				}
				if (!me.hasFocus) {
					me.hasFocus = true;
					me.trigger('focus', me, e);
				}
			}
		},

		onShow : function() {
			this.updateLayout({
				isRoot : false
			});
		},

		/**
		 * Removes a cls to the uiCls array, which will also call {@link #removeUIClsFromElement} and removes it from all
		 * elements of this component.
		 * @param {String/String[]} cls A string or an array of strings to remove to the uiCls
		 */
		removeClsWithUI : function(classes, skip) {
			var me = this, clsArray = [], i = 0, length, cls;

			if ( typeof classes === "string") {
				classes = (classes.indexOf(' ') < 0) ? [classes] : Ext.String.splitWords(classes);
			}

			length = classes.length;

			for ( i = 0; i < length; i++) {
				cls = classes[i];
				if (cls && me.hasUICls(cls)) {
					me.uiCls = aries.Array.remove(me.uiCls, cls);
					clsArray = clsArray.concat(me.removeUIClsFromElement(cls));
				}
			}

			if (skip !== true) {
				me.removeCls(clsArray);
			}

			return clsArray;
		},

		/**
		 * Enable or disable the component.
		 * @param {Boolean} disabled True to disable.
		 */
		setDisabled : function(disabled) {
			return this[disabled ? 'disable': 'enable']();
		},

		/**
		 * Sets the height of the component. This method fires the {@link #resize} event.
		 *
		 * @param {Number} height The new height to set. This may be one of:
		 *
		 *   - A Number specifying the new height in the {@link #getEl Element}'s {@link Ext.Element#defaultUnit}s (by default, pixels).
		 *   - A String used to set the CSS height style.
		 *   - _undefined_ to leave the height unchanged.
		 *
		 * @return {Ext.Component} this
		 */
		setHeight : function(height) {
			this.setSize(undefined, height);
		},

		/**
		 * Sets the width and height of this Component. This method fires the {@link #resize} event. This method can accept
		 * either width and height as separate arguments, or you can pass a size object like `{width:10, height:20}`.
		 *
		 * @param {Number/String/Object} width The new width to set. This may be one of:
		 *
		 *   - A Number specifying the new width in the {@link #getEl Element}'s {@link Ext.Element#defaultUnit}s (by default, pixels).
		 *   - A String used to set the CSS width style.
		 *   - A size object in the format `{width: widthValue, height: heightValue}`.
		 *   - `undefined` to leave the width unchanged.
		 *
		 * @param {Number/String} height The new height to set (not required if a size object is passed as the first arg).
		 * This may be one of:
		 *
		 *   - A Number specifying the new height in the {@link #getEl Element}'s {@link Ext.Element#defaultUnit}s (by default, pixels).
		 *   - A String used to set the CSS height style. Animation may **not** be used.
		 *   - `undefined` to leave the height unchanged.
		 *
		 * @return {Ext.Component} this
		 */
		setSize : function(width, height) {
			var me = this;

			// support for standard size objects
			if (width && typeof width == 'object') {
				height = width.height;
				width = width.width;
			}

			// Constrain within configured maxima
			if ( typeof width == 'number') {
				me.width = aries.Number.constrain(width, me.minWidth, me.maxWidth);
			} else if (width === null) {
				delete me.width;
			}

			if ( typeof height == 'number') {
				me.height = aries.Number.constrain(height, me.minHeight, me.maxHeight);
			} else if (height === null) {
				delete me.height;
			}

			// If not rendered, all we need to is set the properties.
			// The initial layout will set the size
			if (me.rendered && me.isVisible()) {
				/*console.log(me.$el.css({
				 height : me.height,
				 width : me.width
				 }))*/
				me.$el.width(me.width).height(me.height);
				// If we are changing size, then we are not the root.
				/*me.updateLayout({
				 isRoot : false
				 });*/
			}

			return me;
		},

		/**
		 * Sets the width of the component. This method fires the {@link #resize} event.
		 *
		 * @param {Number} width The new width to setThis may be one of:
		 *
		 *   - A Number specifying the new width in the {@link #getEl Element}'s {@link Ext.Element#defaultUnit}s (by default, pixels).
		 *   - A String used to set the CSS width style.
		 *
		 * @return {Ext.Component} this
		 */
		setWidth : function(width) {
			var outerWidth;
			if ( outerWidth = this.$el.outerWidth() - this.$el.width()) {
				width = width - outerWidth;
			}

			return this.setSize(width);
		},

		/**
		 * Sets the UI for the component. This will remove any existing UIs on the component. It will also loop through any
		 * uiCls set on the component and rename them so they include the new UI
		 * @param {String} ui The new UI for the component
		 */
		setUI : function(ui) {
			var me = this, oldUICls = _.clone(_.isString(me.uiCls) ? me.uiCls.split(rspace) : me.uiCls), newUICls = [], classes = [], cls, i;

			//loop through all existing uiCls and update the ui in them
			for ( i = 0; i < oldUICls.length; i++) {
				cls = oldUICls[i];

				classes = classes.concat(me.removeClsWithUI(cls, true));
				newUICls.push(cls);
			}

			if (classes.length) {
				me.removeCls(classes);
			}

			//remove the UI from the element
			me.removeUIFromElement();

			//set the UI
			me.ui = ui;

			//add the new UI to the element
			me.addUIToElement();

			//loop through all existing uiCls and update the ui in them
			classes = [];
			for ( i = 0; i < newUICls.length; i++) {
				cls = newUICls[i];
				classes = classes.concat(me.addClsWithUI(cls, true));
			}

			if (classes.length) {
				me.addCls(classes);
			}

			// Changing the ui can lead to significant changes to a component's appearance, so the layout needs to be
			// updated. Internally most calls to setUI are pre-render. Buttons are a notable exception as setScale changes
			// the ui and often requires the layout to be updated.
			if (me.rendered) {
				me.updateLayout();
			}
		},

		setupProtoEl : function() {
			var me = this, cls = [me.baseCls/*, me.getComponentLayout().targetCls*/];

			//<deprecated since=0.99>
			if (!_.isUndefined(me.cmpCls)) {
				if (!_.isUndefined(console)) {
					console.warn('Ext.Component: cmpCls has been deprecated. Please use componentCls.');
				}
				me.componentCls = me.cmpCls;
				delete me.cmpCls;
			}
			//</deprecated>

			if (me.componentCls) {
				cls.push(me.componentCls);
			} else {
				me.componentCls = me.baseCls;
			}
			this.$el.addClass(cls.join(' '));
			/*me.protoEl = new Ext.util.ProtoElement({
			 cls : cls.join(' ') // in case any of the parts have multiple classes
			 });*/
		},

		/**
		 * Walks up the `ownerCt` axis looking for an ancestor Container which matches the passed simple selector.
		 *
		 * Example:
		 *
		 *     var owningTabPanel = grid.up('tabpanel');
		 *
		 * @param {String} [selector] The simple selector to test.
		 * @return {Ext.container.Container} The matching ancestor Container (or `undefined` if no match was found).
		 */
		up : function(selector) {
			// Use bubble target to navigate upwards so that Components can implement their own hierarchy.
			// For example Menus implement getBubbleTarget because they have a parentMenu or ownerButton as an
			// upward link depending upon how they are owned and triggered.
			var result = this.getBubbleTarget();
			if (selector) {
				for (; result; result = result.getBubbleTarget()) {
					if (Ext.ComponentQuery.is(result, selector)) {
						return result;
					}
				}
			}
			return result;
		},

		/**
		 * Updates this component's layout. If this update effects this components {@link #ownerCt},
		 * that component's `updateLayout` method will be called to perform the layout instead.
		 * Otherwise, just this component (and its child items) will layout.
		 *
		 * @param {Object} options An object with layout options.
		 * @param {Boolean} options.defer True if this layout should be deferred.
		 * @param {Boolean} options.isRoot True if this layout should be the root of the layout.
		 */
		updateLayout : function(options) {
			var me = this, defer, isRoot = options && options.isRoot;

			if (!me.rendered || me.layoutSuspendCount || me.suspendLayout) {
				return;
			}

			if (me.hidden) {
				Ext.AbstractComponent.cancelLayout(me);
			} else if ( typeof isRoot != 'boolean') {
				//isRoot = me.isLayoutRoot();
			}

			// if we aren't the root, see if our ownerLayout will handle it...
			/*if (isRoot || !me.ownerLayout || !me.ownerLayout.onContentChange(me)) {
			 // either we are the root or our ownerLayout doesn't care
			 if (!me.isLayoutSuspended()) {
			 // we aren't suspended (knew that), but neither is any of our ownerCt's...
			 defer = (options && options.hasOwnProperty('defer')) ? options.defer : me.deferLayouts;
			 Ext.AbstractComponent.updateLayout(me, defer);
			 }
			 }*/
		},

		/**
		 * Returns true if this component is visible.
		 *
		 * @param {Boolean} [deep=false] Pass `true` to interrogate the visibility status of all parent Containers to
		 * determine whether this Component is truly visible to the user.
		 *
		 * Generally, to determine whether a Component is hidden, the no argument form is needed. For example when creating
		 * dynamically laid out UIs in a hidden Container before showing them.
		 *
		 * @return {Boolean} True if this component is visible, false otherwise.
		 */
		isVisible : function(deep) {
			var me = this, child = me, visible = me.rendered && !me.hidden, ancestor = me.ownerCt;

			// Clear hiddenOwnerCt property
			me.hiddenAncestor = false;
			if (me.destroyed) {
				return false;
			}

			/*if (deep && visible && ancestor) {
			 while (ancestor) {
			 // If any ancestor is hidden, then this is hidden.
			 // If an ancestor Panel (only Panels have a collapse method) is collapsed,
			 // then its layoutTarget (body) is hidden, so this is hidden unless its within a
			 // docked item; they are still visible when collapsed (Unless they themseves are hidden)
			 if (ancestor.hidden || (ancestor.collapsed && !(ancestor.getDockedItems && Ext.Array.contains(ancestor.getDockedItems(), child)))) {
			 // Store hiddenOwnerCt property if needed
			 me.hiddenAncestor = ancestor;
			 visible = false;
			 break;
			 }
			 child = ancestor;
			 ancestor = ancestor.ownerCt;
			 }
			 }*/
			return visible;
		}
	})).mixin({
		observable : aries.util.Observable,
		renderable : aries.util.Renderable,
		elementCt : aries.util.ElementContainer
	});
})
